跳到主要内容

11-12 调试利器 GDB

阅读量

0

阅读人次

0

使用GDB Server和VS Code

在嵌入式板子上,直接使用gdb,需要记住各种gdb命令,时间久了就忘的差不多了。在条件许可的情况下,GDB Server和VS Code配合使用,会非常方便调试。

这里加上了useExtendedRemotesetupCommands两个字段,用到了GDB Server的extended-remote模式,使得我们可以不用每次在板子上通过GDB Server运行程序或者附加到已经运行的程序,然后再使用VS Code + GDB调试。而是直接通过VSCode集成方便的启动调试程序。

.vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "AppDebug",
"type": "cppdbg",
"request": "launch",
"miDebuggerPath": "arm-linux-gnueabihf-gdb",
"miDebuggerServerAddress": "192.168.8.127:8080",
"program": "${workspaceFolder}/build/GateFace",
"args": [],
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"logging": {
"engineLogging": false
},
"MIMode": "gdb",
"useExtendedRemote": true,
"setupCommands": [
{
"text": "set remote exec-file /sdcard/GateFace",
"description": "设置嵌入式单板加载的程序",
"ignoreFailures": false
}
]
}
]
}

然后在板子上执行:

./gdbserver --multi 192.168.8.127:8080

就可以很方便的使用VS Code的 运行和调试 功能了。

如果希望手动在单板上运行调试程序或附加到已经运行的调试程序,useExtendedRemotesetupCommands两个字段去除即可。

target extended-remote 192.168.8.127:8080
set remote exec-file /sdcard/GateFace

源码编译

下载GDB源码包,这里以 gdb-14.1.tar.xz 为例。下载后进行解压然后进入源码目录:

tar xvf ./gdb-14.1.tar.xz
cd gdb-14.1/

首先构建gdb(构建出来的 aarch64-linux-gnu-gdb 是运行在 PC 环境下的,所以使用的编译器不是交叉编译工具,而是本地 gcc ):

sudo apt install libgmp-dev libmpfr-dev texinfo
./configure --enable-static=yes --target=aarch64-linux-gnu --prefix=/opt/aarch64-v01c01-linux-gnu-gcc/lib/gdb-14.1
make -j6 && make install

然后构建运行于目标调试板的 gdbserver(可以删除 gdb-14.1 目录,重新解压源码以保持环境干净):

export CC=aarch64-linux-gnu-gcc
./configure --enable-static=yes --host aarch64-linux-gnu --target=aarch64-linux-gnu --disable-gdb --prefix=/opt/aarch64-v01c01-linux-gnu-gcc/lib/gdb-14.1
make all-gdbserver -j6 && make install-gdbserver

在使用时 gdb 会报错:

Remote 'g' packet reply is too long (expected 788 bytes, got 796 bytes): ......

如果出现此报错,则降低gdb版本尝试。推荐使用和交叉编译工具链gcc版本相同的gdb源码。

11 调试利器GDB(上)

什么是GDB?

GNU项目中的调试器(gnu debuger);能够跟踪程序的执行,也能够恢复程序崩溃前的状态。

为什么需要GDB?

软件不是一次性开发完成的(是软件就有bug,是程序就有问题);调试是软件开发过程中不可或缺的技术(调试工具很重要)。

GDB的常规应用

  • 自定义程序的启动方式(指定影响程序运行的参数)
  • 设置条件断点(在条件满足时暂停程序的执行)
  • 回溯检查导致程序异常结束的原因(Core Dump)
  • 动态改变程序执行流(定位问题的辅助方式)

GDB的启动方式

  • 直接启动
    • gdb
    • gdb test.out
    • gdb test.out core
  • 动态连接:gdb test.out pid

GDB应用示例一

kylin@hp-vm:~$ gdb			//启动
(gdb) file test.out //载入目标程序,等价于gdb test.out
(gdb) set args arg1 arg2 //设置命令行参数
(gdb) run //执行目标程序

GDB应用示例二

kylin@hp-vm:~$ gdb	//启动
(gdb) attach pid //链接到目标进程,链接成功后目标进程将停止执行。等价于gdb test.out pid
(gdb) continue //恢复执行

使用GDB进行断点调试

  • 断点类型

    • 软件断点:由非法指令异常实现(软件实现)
    • 硬件断点:由硬件特性实现(数量有限)
    • 数据断点:由硬件特性实现(数量有限)
  • 软件断点的相关操作

    • 通过函数名设置断点
      • break func_name[if var = value]
      • tbreak func_name[if var = value]
    • 通过文件名行号设置断点
      • break file_name:line_num[if var = value]
      • tbreak file_name:line_num[if var=value]

软件断点的相关操作

操作命令
断点查看info breakpoints
断点删除delete 1 2 n delete breakpoints
断点状态改变enable 1 2 n enable breakpoints disable 1 2 n disable breakpoints

调试时的常用操作

操作命令
变量查看print name
变量设置set var name = value
执行下一行代码next
连续执行n行代码next n
执行进入函数step
强制当前函数返回return [value]
运行至当前函数返回finish
执行至目标行until line
跳转执行jump line

硬件断点及其应用

  • 当代码位于只读存储器(Flash)时,只能通过硬件断点调试
  • 硬件断点需要硬件支持,数量有限
  • GDB中通过hbreak命令支持硬件断点
  • hbreak与break使用方式完全一致

编程实验:使用GDB进行断点调试

//func.c
#include <stdio.h>

int* g_pointer;

void func() {
*g_pointer = (int)"D.T.Software";
return;
}
//test.c
#include <stdio.h>
#include <unistd.h>

extern int* g_pointer;
extern void func();

void test_1() {
printf("test_1() : %p\n", test_1);
}

void test_2() {
printf("test_2() : %p\n", test_2);
}

void test_3() {
printf("test_3() : %p\n", test_3);
}

int main(int argc, char *argv[]) {
typedef void(TFunc)();
TFunc* fa[] = {test_1, test_2, test_3};
int i = 0;

printf("main() : begin...\n");

for(i=0; i<argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}

for(i=0; i<100; i++) {
fa[i%3]();
sleep(argc > 1);
}

printf("g_pointer = %p\n", g_pointer);

func();

printf("main() : end...\n");

return 0;
}

小结

  • GDB是GNU项目中的调试器,能够跟踪或改变程序的执行
  • GDB能够根据Core Dump回溯检查导致程序异常结束的原因
  • GDB同时支持软件断点,硬件断点和数据断点
  • GDB是嵌入式开发中必须掌握的重要工具

12 调试利器GDB(下)

数据断点

  • GDB中支持数据断点的设置
  • watch命令用于监视变量是否被改变(本质为硬件断点)
  • watch命令的用法:watch var_name

GDB中的内存查看

  • GDB中可以检查任意内存区域中的数据
  • 命令语法: x /Nuf expression
    • N - 需要打印的单元数
    • u - 每个单元的大小
    • f - 数据打印的格式
示例 :   x /4bx 0x804a024
  • x命令中参数u对应的单位

    格式打印方式
    b单字节
    h双字节
    w四字节
    g八字节
  • GDB中的打印格式

    格式打印方式
    x十六进制
    d有符号十进制
    u无符号十进制
    o八进制
    t二进制
    a地址
    c字符
    f浮点数
  • 示例:判断系统大小端

    (gdb) set var=1
    (gdb)print /a &var
    $1 = 0x804a024<var>
    (gdb) x /4bx 0x804a024
    0x804a024<var>: 0x01 0x00 0x00 0x00
    (gdb) x /1bx 0x804a024
    0x804a024<var>:0x01

编程实验一:变量断点和内存查看

//watch.c
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

int g_var = 0;

void* thread_func(void* args) {
sleep(5);

g_var = 1;
}

int main() {
int i = 0;
pthread_t tid = 0;

pthread_create(&tid, NULL, thread_func, NULL);

for(i=0; i<10; i++) {
printf("g_var = %d\n", g_var);

sleep(1);
}
}

函数调用栈的查看(backtrace和frame)

  • backtrace:查看函数调用的顺序(函数调用栈的信息)
  • frame N : 切换到栈编号为N的上下文中
  • info frame : 查看当前函数调用的栈帧信息

什么是栈帧信息?

深入info命令

命令功能说明
info registers查看当前寄存器的值
info args查看当前函数参数的值
info locals查看当前局部变量的值
info frame查看当前栈帧的详细信息
info variables查看程序中的变量符号
info functions查看程序中的函数符号

编程实验二:函数调用栈的查看

#include <stdio.h>

int sum(int n) {
int ret = 0;

if( n > 0 ) {
ret = n + sum(n-1);
}

return ret;
}

int main() {
int s = 0;

s = sum(10);

printf("sum = %d\n", s);

return 0;
}

一些调试中的小技巧

操作命令
断点处自动打印display /f expression undisplay
查看程序中的符号whatis ptype
GDB中的代码查看list set listsize N
GDB中的shell操作shell

技巧示例:断点处自动打印

(gdb) shell gcc -g test.c -o test.out
(gdb) file test.out
Reading symbols form /home/kylin/test.out...done.
(gdb)break test.c:18
Breakpoint 2 at 0x80483ef:file test.c,line 18.
(gdb)continue
Continuing.
Breakpoint 2,func() at test.c:18
18 st[i].i=i;
(gdb) display /d i
1: /d i = 0
(gdb) display /d i*i
2: /d i*i = 0
(gdb) display /a &i
3: /a &i = 0xbffff09c

技巧示例:符号查看

(gdb) whatis func
type = int()
(gdb) ptype func
type = int()
(gdb)whatis g_var
type=int
(gdb) ptype g_var
type = int
(gdb)whatis struct ST
type = struct ST
(gdb)ptype struct ST
type = struct ST{int i;int j};

编程实验三:调试中的小技巧

#include <stdio.h>

int g_var = 1;

struct ST {
int i;
int j;
};

int func() {
struct ST st[5] = {0};
int i = 0;

for(i=0; i<5; i++) {
st[i].i = i;
st[i].j = i * i;
}

for(i=0; i<5; i++) {
printf("st[%d].i = %d\n", i, st[i].i);
printf("st[%d].j = %d\n", i, st[i].j);
}
}

int main() {
static c_var = 2;
func();
return 0;
}

小结

  • GDB支持数据断点的设置(一种类型的硬件断点)
  • watc用于监视变量是否被改变,x用于查看内存中的数据
  • GDB支持函数调用栈的查看(backtrace,info frames)
  • GDB支持运行时对程序的符号进行查看(whatis,ptype)